▍程式碼
<!-- quiz_report.html -->
<head>
<title>測驗成績統計與報表</title>
<!-- 導入樣式、圖表資源 -->
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head>
<body>
<div>
<h1>📊 測驗成績分析報告</h1>
<div id="summary-section">
<h2>總成績概覽</h2>
<div>
<div>
<p>總題數</p>
<p id="total-questions">0</p>
</div>
<div>
<p>答對題數 / 總分</p>
<p id="correct-count">0 / 0</p>
</div>
<div>
<p>總正確率</p>
<p id="overall-accuracy">0%</p>
</div>
</div>
</div>
<!-- 圖表 -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
<!-- 圓餅圖:題目分佈 -->
<div class="lg:col-span-1 container-card bg-white p-6 rounded-xl flex flex-col items-center">
<h2>章節(來源書籍)題目分佈</h2>
<div class="w-full h-80">
<canvas id="topicDistributionChart"></canvas>
</div>
</div>
<!-- 長條圖:章節正確率 -->
<div class="lg:col-span-2 container-card bg-white p-6 rounded-xl">
<h2>弱點章節分析 (正確率長條圖)</h2>
<div class="w-full h-96">
<canvas id="accuracyBarChart"></canvas>
</div>
</div>
</div>
<!-- 表格 -->
<div>
<h2>📚 詳細章節成績</h2>
<div>
<table>
<thead>
<tr>
<th scope="col">來源書籍 (章節)</th>
<th scope="col">總題數</th>
<th scope="col">答對題數</th>
<th scope="col">正確率</th>
<th scope="col">弱點指標</th>
</tr>
</thead>
<tbody id="accuracy-table-body">
<!-- 錯題資訊 -->
</tbody>
</table>
</div>
</div>
</div>
<script>
// 後端接收數據
const quizResults = {{ quiz_results | tojson | safe }};
const SCORE_PER_QUESTION = 2;
// 圖表顏色配置
const ACCURACY_COLORS = [...];
// 處理測驗結果,計算成績、正確率
function analyzeResults(results) {
let totalQuestions = results.length;
let correctCount = 0;
const bookAccuracy = {};
results.forEach(item => {
// 處理來源書籍,如果為空則設為無
const book = item.book_source || '無';
const isCorrect = item.is_correct;
if (isCorrect) {
correctCount++;
}
// 更新章節數據
if (!bookAccuracy[book]) {
bookAccuracy[book] = { total: 0, correct: 0, accuracy: 0, color: '' };
}
bookAccuracy[book].total++;
if (isCorrect) {
bookAccuracy[book].correct++;
}
});
// 計算各章節準確率
const sortedBooks = Object.keys(bookAccuracy).sort();
sortedBooks.forEach((book, index) => {
const data = bookAccuracy[book];
data.accuracy = data.total > 0 ? (data.correct / data.total) * 100 : 0;
data.color = ACCURACY_COLORS[index % ACCURACY_COLORS.length];
});
return {
totalQuestions,
correctCount,
bookAccuracy,
};
}
// 渲染成績概覽
function renderSummary(summary) {
const { totalQuestions, correctCount } = summary;
const totalScore = correctCount * SCORE_PER_QUESTION;
const overallAccuracy = totalQuestions > 0 ? (correctCount / totalQuestions * 100).toFixed(1) : 0;
document.getElementById('total-questions').textContent = totalQuestions;
document.getElementById('correct-count').textContent = `${correctCount} / ${totalScore}`;
document.getElementById('overall-accuracy').textContent = `${overallAccuracy}%`;
}
// 渲染表格
function renderTable(bookAccuracy) {
const tbody = document.getElementById('accuracy-table-body');
tbody.innerHTML = '';
// 章節按正確率排列
const sortedEntries = Object.entries(bookAccuracy)
.sort(([, a], [, b]) => a.accuracy - b.accuracy);
sortedEntries.forEach(([book, data]) => {
const row = tbody.insertRow();
row.className = 'hover:bg-gray-100';
// 弱點標記 → 題目數 > 1 & 正確率低於 60%
const isWeakSpot = data.accuracy < 60 && data.total > 1;
const indicatorClass = isWeakSpot ? 'bg-red-500 text-white font-semibold rounded-full p-1 text-xs text-center' : 'bg-green-500 text-white rounded-full p-1 text-xs text-center';
const indicatorText = isWeakSpot ? '⚠️ 需加強' : '✅ 掌握';
row.insertCell().textContent = book;
row.insertCell().textContent = data.total;
row.insertCell().textContent = data.correct;
row.insertCell().textContent = `${data.accuracy.toFixed(1)}%`;
const indicatorCell = row.insertCell();
indicatorCell.innerHTML = `<span class="${indicatorClass}" style="min-width: 80px; display: inline-block;">${indicatorText}</span>`;
});
}
// 避免重複渲染
let distributionChartInstance = null;
let accuracyChartInstance = null;
// 渲染圓餅圖
function renderDistributionChart(bookAccuracy) {
if (distributionChartInstance) {
distributionChartInstance.destroy();
}
const labels = Object.keys(bookAccuracy);
const data = labels.map(book => bookAccuracy[book].total);
const colors = labels.map(book => bookAccuracy[book].color);
distributionChartInstance = new Chart(document.getElementById('topicDistributionChart'), {
type: 'pie',
data: {
labels: labels,
datasets: [{
label: '題目數量',
data: data,
backgroundColor: colors,
hoverOffset: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
},
title: {
display: false
}
}
}
});
}
// 渲染長條圖
function renderAccuracyChart(bookAccuracy) {
if (accuracyChartInstance) {
accuracyChartInstance.destroy();
}
// 按正確率排列
const sortedEntries = Object.entries(bookAccuracy)
.sort(([, a], [, b]) => a.accuracy - b.accuracy);
const labels = sortedEntries.map(([book]) => book);
const data = sortedEntries.map(([, data]) => parseFloat(data.accuracy.toFixed(1)));
const colors = sortedEntries.map(([, data]) => data.color);
accuracyChartInstance = new Chart(document.getElementById('accuracyBarChart'), {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '正確率 (%)',
data: data,
backgroundColor: colors,
borderColor: colors,
borderWidth: 1,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y', // 水平長條圖
scales: {
x: {
beginAtZero: true,
max: 100,
title: {
display: true,
text: '正確率 (%)'
}
}
},
plugins: {
legend: {
display: false,
},
title: {
display: true,
text: '章節正確率 (分數越低越需優先加強)'
}
}
}
});
}
window.onload = function() {
if (!quizResults || quizResults.length === 0) {
document.getElementById('summary-section').innerHTML = '<p class="text-center text-xl text-red-500">錯誤:找不到測驗結果數據可供分析。請先完成測驗。</p>';
return;
}
const summary = analyzeResults(quizResults);
renderSummary(summary);
renderTable(summary.bookAccuracy);
renderDistributionChart(summary.bookAccuracy);
renderAccuracyChart(summary.bookAccuracy);
};
</script>
</body>
# views.py
@app.route('/report_page')
def report_page():
report_data = TEMP_QUIZ_RESULTS.get('latest_report')
if not report_data:
return redirect(url_for('quiz'))
return render_template('quiz_report.html', quiz_results=report_data['detailed_results'])